package db

import (
	"center/db/models"
	"common/node_pieces"
	"common/uuid"
	"context"
	"database/sql"
	"errors"
	time2 "github.com/wuzfei/go-helper/time"
	"go.uber.org/zap"
	"gorm.io/gorm"
	"time"
)

func (d *CenterDB) CreateRepairOld(ctx context.Context, r models.Repair) error {
	err := d.db.WithContext(ctx).Create(&r).Error
	return err
}

// CreateRepairs 批量创建修复记录
func (d *CenterDB) CreateRepairs(ctx context.Context, repairs []*models.Repair) error {
	if len(repairs) == 0 {
		return nil
	}
	return d.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		err := tx.Create(repairs).Error
		if err != nil {
			return err
		}
		segIds := make([]int64, len(repairs))
		for i := range repairs {
			segIds[i] = repairs[i].SegmentID
		}
		//更新segment为修复中
		return tx.Model(models.Segment{}).
			Where("id in ?", segIds).UpdateColumn("status", models.StatusRepair).Error
	})
}

func (d *CenterDB) ResetRepairWaitTooLong(ctx context.Context, t time.Duration) error {
	if int(t) == 0 {
		return nil
	}
	return d.db.WithContext(ctx).Model(models.Repair{}).
		Where("status = ? and start_time <= ?", models.RepairStatusRepairing, time.Now().Add(-t).Format(time2.DefaultDateTimeLayout)).
		Updates(map[string]any{
			"status": models.RepairStatusError,
		}).Error
}

// GetRepairTask 获取一个修复任务
func (d *CenterDB) GetRepairTask(ctx context.Context, nodeId uuid.UUID) (obj *models.Object, seg *models.Segment, rep *models.Repair, err error) {
	//获取修复数据，并更新
	err = d.db.Transaction(func(tx *gorm.DB) error {
		err = d.db.WithContext(ctx).Model(models.Repair{}).
			Where("Status != ?", models.RepairStatusRepairing).
			Order("retry asc").
			Limit(1).Take(&rep).Error
		if err != nil {
			return err
		}
		res := d.db.WithContext(ctx).Model(models.Repair{}).
			Where("segment_id = ? and status = ?", rep.SegmentID, rep.Status).
			Updates(map[string]any{
				"status":     models.RepairStatusRepairing,
				"node_id":    nodeId,
				"start_time": sql.NullTime{Time: time.Now(), Valid: true},
			})
		if res.Error != nil {
			return res.Error
		}
		if res.RowsAffected == 0 {
			return errors.New("GetRepairTask when update error")
		}
		return nil
	})
	if err != nil {
		return
	}

	//获取对象，过期了就不用修复了
	obj, err = d.GetObjectByStreamId(ctx, rep.StreamId.Bytes())
	if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
		return
	}
	if (err != nil && errors.Is(err, gorm.ErrRecordNotFound)) || obj.Expired() {
		//todo 考虑可以把整个文件都删了
		//删除待修复的数据
		err = d.db.WithContext(ctx).Model(models.Repair{}).Where("stream_id = ?", obj.StreamId).Delete(&models.Repair{}).Error
		if err != nil {
			return
		}
		err = errors.New("object expire")
		return
	}

	//获取segment
	seg, err = d.GetSegments(ctx, rep.StreamId, rep.Position)
	return
}

// CommitRepairTask 修复结果提交
func (d *CenterDB) CommitRepairTask(ctx context.Context, streamId uuid.UUID, idx int32, nodePieces node_pieces.NodePieces) (err error) {
	defer func() {
		if err != nil {
			d.log.Error("CommitRepairTask error", zap.ByteString("streamId", streamId.Bytes()),
				zap.Int32("position", idx), zap.Error(err))
		}
		d.log.Info("CommitRepairTask", zap.ByteString("streamId", streamId.Bytes()),
			zap.Int32("position", idx), zap.ByteString("nodePieces", nodePieces.Bytes()))
	}()
	m := models.Repair{}
	err = d.db.WithContext(ctx).Where("stream_id = ? and position = ?", streamId, idx).Take(&m).Error
	if err != nil {
		return
	}
	ln := len(nodePieces)
	//修复失败
	if nodePieces == nil || ln == 0 || ln <= int(m.Redundancy.Required) {
		err = d.db.WithContext(ctx).Model(models.Repair{}).Where("segment_id = ?", m.SegmentID).Updates(map[string]any{
			"status": models.RepairStatusError,
			"retry":  m.Retry + 1,
		}).Error
		if err != nil {
			return
		}
	}

	//未达到修复数量, 会更新segment，但是要继续修复
	if ln <= int(m.Redundancy.Repair) {
		err = d.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
			err = d.db.WithContext(ctx).Model(models.Segment{}).Where("id = ?", m.SegmentID).
				UpdateColumns(map[string]any{
					"node_pieces": nodePieces,
					"folded":      nodePieces.Folded(),
				}).Error
			if err != nil {
				return err
			}
			return d.db.WithContext(ctx).Model(models.Repair{}).Where("segment_id = ?", m.SegmentID).Updates(map[string]any{
				"status": models.RepairStatusError,
				"retry":  m.Retry + 1,
			}).Error
		})
		if err != nil {
			return
		}
	}

	//修复成功
	err = d.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		err = d.db.WithContext(ctx).Model(models.Segment{}).Where("id = ?", m.SegmentID).
			Updates(map[string]any{
				"node_pieces": nodePieces,
				"folded":      nodePieces.Folded(),
				"status":      models.StatusNormal,
			}).Error
		if err != nil {
			return err
		}
		//删除修复表记录
		return d.db.WithContext(ctx).Model(models.Repair{}).Where("segment_id = ?", m.SegmentID).
			Delete(&models.Repair{}).Error
	})
	return
}
